//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////////////////
//
// apiMeshShapeUI.cpp
//
////////////////////////////////////////////////////////////////////////////////

#include <maya/MIOStream.h>

#include <apiMeshShapeUI.h>

#include <maya/MMaterial.h>
#include <maya/MDrawData.h>
#include <maya/MMatrix.h>
#include <maya/MFnSingleIndexedComponent.h>
#include <maya/MObjectArray.h>
#include <maya/MDagPath.h>

// Object and component color defines
//
#define LEAD_COLOR				18	// green
#define ACTIVE_COLOR			15	// white
#define ACTIVE_AFFECTED_COLOR	8	// purple
#define DORMANT_COLOR			4	// blue
#define HILITE_COLOR			17	// pale blue
#define DORMANT_VERTEX_COLOR	8	// purple
#define ACTIVE_VERTEX_COLOR		16	// yellow

// Vertex point size
//
#define POINT_SIZE				2.0	

////////////////////////////////////////////////////////////////////////////////
//
// UI implementation
//
////////////////////////////////////////////////////////////////////////////////

apiMeshUI::apiMeshUI() {}
apiMeshUI::~apiMeshUI() {}

void* apiMeshUI::creator()
{
	return new apiMeshUI();
}

///////////////////////////////////////////////////////////////////////////////
//
// Overrides
//
///////////////////////////////////////////////////////////////////////////////

/* override */
void apiMeshUI::getDrawRequests( const MDrawInfo & info,
							 bool objectAndActiveOnly,
							 MDrawRequestQueue & queue )
//
// Description:
//
//     Add draw requests to the draw queue
//
// Arguments:
//
//     info                 - current drawing state
//     objectsAndActiveOnly - no components if true
//     queue                - queue of draw requests to add to
//
{
	// Get the data necessary to draw the shape
	//
	MDrawData data;
	apiMesh* meshNode = (apiMesh*)surfaceShape();
	apiMeshGeom * geom = meshNode->meshGeom();
	if ( (NULL == geom) || (0 == geom->faceCount) ) {
		cerr << "NO DrawRequest for apiMesh\n";
		return;
	}

	// This call creates a prototype draw request that we can fill
	// in and then add to the draw queue.
	//
	MDrawRequest request = info.getPrototype( *this );
	getDrawData( geom, data );
	request.setDrawData( data );

	// Decode the draw info and determine what needs to be drawn
	//

	M3dView::DisplayStyle  appearance    = info.displayStyle();
	M3dView::DisplayStatus displayStatus = info.displayStatus();
	
	// Are we displaying meshes?
	if ( ! info.objectDisplayStatus( M3dView::kDisplayMeshes ) )
		return;
	
	switch ( appearance )
	{
		case M3dView::kWireFrame :
		{
			request.setToken( kDrawWireframe );

			M3dView::ColorTable activeColorTable = M3dView::kActiveColors;
			M3dView::ColorTable dormantColorTable = M3dView::kDormantColors;

			switch ( displayStatus )
			{
				case M3dView::kLead :
					request.setColor( LEAD_COLOR, activeColorTable );
					break;
				case M3dView::kActive :
					request.setColor( ACTIVE_COLOR, activeColorTable );
					break;
				case M3dView::kActiveAffected :
					request.setColor( ACTIVE_AFFECTED_COLOR, activeColorTable );
					break;
				case M3dView::kDormant :
					request.setColor( DORMANT_COLOR, dormantColorTable );
					break;
				case M3dView::kHilite :
					request.setColor( HILITE_COLOR, activeColorTable );
					break;
				default:	
					break;
			}

			queue.add( request );

			break;
		}

		case M3dView::kGouraudShaded :
		{
			// Create the smooth shaded draw request
			//
			request.setToken( kDrawSmoothShaded );

			// Need to get the material info
			//
			MDagPath path = info.multiPath();	// path to your dag object 
			M3dView view = info.view();; 		// view to draw to
			MMaterial material = MPxSurfaceShapeUI::material( path );

			// Evaluate the material and if necessary, the texture.
			//
			if ( ! material.evaluateMaterial( view, path ) ) {
				cerr << "Couldnt evaluate\n";
			}

			bool drawTexture = true;
			if ( drawTexture && material.materialIsTextured() ) {
				material.evaluateTexture( data );
			}

			request.setMaterial( material );

			// request.setDisplayStyle( appearance );

			bool materialTransparent = false;
			material.getHasTransparency( materialTransparent );
			if ( materialTransparent ) {
				request.setIsTransparent( true );
			}

			queue.add( request );

			// create a draw request for wireframe on shaded if
			// necessary.
			//
			if ( (displayStatus == M3dView::kActive) ||
				 (displayStatus == M3dView::kLead) ||
				 (displayStatus == M3dView::kHilite) )
			{
				MDrawRequest wireRequest = info.getPrototype( *this );
				wireRequest.setDrawData( data );
				wireRequest.setToken( kDrawWireframeOnShaded );
				wireRequest.setDisplayStyle( M3dView::kWireFrame );

				M3dView::ColorTable activeColorTable = M3dView::kActiveColors;

				switch ( displayStatus )
				{
					case M3dView::kLead :
						wireRequest.setColor( LEAD_COLOR, activeColorTable );
						break;
					case M3dView::kActive :
						wireRequest.setColor( ACTIVE_COLOR, activeColorTable );
						break;
					case M3dView::kHilite :
						wireRequest.setColor( HILITE_COLOR, activeColorTable );
						break;
					default:	
						break;

				}

				queue.add( wireRequest );
			}

			break;
		}

		case M3dView::kFlatShaded :
			request.setToken( kDrawFlatShaded );
			queue.add( request );
			break;
		case M3dView::kBoundingBox :
			request.setToken( kDrawBoundingBox );
			queue.add( request );
			break;
		default:	
			break;
	}

	// Add draw requests for components
	//
	if ( !objectAndActiveOnly ) {

		// Inactive components
		//
		if ( (appearance == M3dView::kPoints) ||
			 (displayStatus == M3dView::kHilite) )
		{
			MDrawRequest vertexRequest = info.getPrototype( *this ); 
			vertexRequest.setDrawData( data );
			vertexRequest.setToken( kDrawVertices );
			vertexRequest.setColor( DORMANT_VERTEX_COLOR,
									M3dView::kActiveColors );

			queue.add( vertexRequest );
		}

		// Active components
		//
		if ( surfaceShape()->hasActiveComponents() ) {

			MDrawRequest activeVertexRequest = info.getPrototype( *this ); 
			activeVertexRequest.setDrawData( data );
		    activeVertexRequest.setToken( kDrawVertices );
		    activeVertexRequest.setColor( ACTIVE_VERTEX_COLOR,
										  M3dView::kActiveColors );

		    MObjectArray clist = surfaceShape()->activeComponents();
		    MObject vertexComponent = clist[0]; // Should filter list
		    activeVertexRequest.setComponent( vertexComponent );

			queue.add( activeVertexRequest );
		}
	}
}

/* override */
void apiMeshUI::draw( const MDrawRequest & request, M3dView & view ) const
//
// Description:
//
//     Main (OpenGL) draw routine
//
// Arguments:
//
//     request - request to be drawn
//     view    - view to draw into
//
{ 
	// Get the token from the draw request.
	// The token specifies what needs to be drawn.
	//
	int token = request.token();

	switch( token )
	{
		case kDrawWireframe :
		case kDrawWireframeOnShaded :
			drawWireframe( request, view );
			break;

		case kDrawSmoothShaded :
			drawShaded( request, view );
			break;

		case kDrawFlatShaded : // Not implemented
			break;

		case kDrawVertices :
			drawVertices( request, view );
			break;
			
		case kDrawBoundingBox:
			drawBoundingBox( request, view );
			break;
	}
}

/* override */
bool apiMeshUI::select( MSelectInfo &selectInfo, MSelectionList &selectionList,
					MPointArray &worldSpaceSelectPts ) const
//
// Description:
//
//     Main selection routine
//
// Arguments:
//
//     selectInfo           - the selection state information
//     selectionList        - the list of selected items to add to
//     worldSpaceSelectPts  -
//
{
	bool selected = false;
	bool componentSelected = false;
	bool hilited = false;

	hilited = (selectInfo.displayStatus() == M3dView::kHilite);
	if ( hilited ) {
		componentSelected = selectVertices( selectInfo, selectionList,
											worldSpaceSelectPts );
		selected = selected || componentSelected;
	}

	if ( !selected ) {

		apiMesh* meshNode = (apiMesh*)surfaceShape();

		// NOTE: If the geometry has an intersect routine it should
		// be called here with the selection ray to determine if the
		// the object was selected.

		selected = true;
		MSelectionMask priorityMask( MSelectionMask::kSelectNurbsSurfaces );
		MSelectionList item;
		item.add( selectInfo.selectPath() );
		MPoint xformedPt;
		if ( selectInfo.singleSelection() ) {
			MPoint center = meshNode->boundingBox().center();
			xformedPt = center;
			xformedPt *= selectInfo.selectPath().inclusiveMatrix();
		}

		selectInfo.addSelection( item, xformedPt, selectionList,
								 worldSpaceSelectPts, priorityMask, false );
	}

	return selected;
}

///////////////////////////////////////////////////////////////////////////////
//
// Helper routines
//
///////////////////////////////////////////////////////////////////////////////

void apiMeshUI::drawWireframe( const MDrawRequest & request,
							   M3dView & view ) const
//
// Description:
//
//     Wireframe drawing routine
//
// Arguments:
//
//     request - request to be drawn
//     view    - view to draw into
//
{
	MDrawData data = request.drawData();
	apiMeshGeom * geom = (apiMeshGeom*)data.geometry();

	int token = request.token();

	bool wireFrameOnShaded = false;
	if ( kDrawWireframeOnShaded == token ) {
		wireFrameOnShaded = true;
	}

	view.beginGL(); 

	// Query current state so it can be restored
	//
	bool lightingWasOn = glIsEnabled( GL_LIGHTING ) ? true : false;
	if ( lightingWasOn ) {
		glDisable( GL_LIGHTING );
	}

	if ( wireFrameOnShaded ) {
		glDepthMask( false );
	}

	// Draw the wireframe mesh
	//
	int vid = 0;			
	for ( int i=0; i<geom->faceCount; i++ )
	{
		glBegin( GL_LINE_LOOP );
		for ( int v=0; v<geom->face_counts[i]; v++ )
		{
			MPoint vertex = geom->vertices[ geom->face_connects[vid++] ];
			glVertex3f( (float)vertex[0], (float)vertex[1], (float)vertex[2] );
		}
		glEnd();
	}

	// Restore the state
	//
	if ( lightingWasOn ) {
		glEnable( GL_LIGHTING );
	}
	if ( wireFrameOnShaded ) {
		glDepthMask( true );
	}

	view.endGL(); 
}

void apiMeshUI::drawShaded( const MDrawRequest & request,
							M3dView & view ) const
//
// Description:
//
//     Shaded drawing routine
//
// Arguments:
//
//     request - request to be drawn
//     view    - view to draw into
//
{
	MDrawData data = request.drawData();
	apiMeshGeom * geom = (apiMeshGeom*)data.geometry();

	view.beginGL(); 

#if		defined(SGI) || defined(MESA)
	glEnable( GL_POLYGON_OFFSET_EXT );
#else
	glEnable( GL_POLYGON_OFFSET_FILL );
#endif

	// Set up the material
	//
	MMaterial material = request.material();
	material.setMaterial( request.multiPath(), request.isTransparent() );

	// Enable texturing
	//
	bool drawTexture = material.materialIsTextured();
	if ( drawTexture ) glEnable(GL_TEXTURE_2D);

	// Apply the texture to the current view
	//
	if ( drawTexture ) {
		material.applyTexture( view, data );
	}

	// Draw the polygons
	//
	int vid = 0;
	int u_len = geom->ucoords.length();
	int v_len = geom->vcoords.length();
	for ( int i=0; i<geom->faceCount; i++ )
	{
		glBegin( GL_POLYGON );
		for ( int v=0; v<geom->face_counts[i]; v++ )
		{
			MPoint vertex = geom->vertices[ geom->face_connects[vid] ];
			MVector normal = geom->normals[ geom->face_connects[vid] ];

			// If we are drawing the texture, make sure the  coord 
			// arrays are in bounds.
			if ( drawTexture && (vid < u_len) && (vid < v_len) ) {
           		glTexCoord2f( (float)geom->ucoords[vid], 
							  (float)geom->vcoords[vid] );
			}

			glNormal3f( (float)normal[0], (float)normal[1], (float)normal[2] );
			glVertex3f( (float)vertex[0], (float)vertex[1], (float)vertex[2] );
			vid++;
		}
		glEnd();
	} 

	// Turn off texture mode
	//
	if ( drawTexture ) glDisable(GL_TEXTURE_2D);

	view.endGL(); 
}

void apiMeshUI::drawVertices( const MDrawRequest & request,
							  M3dView & view ) const
//
// Description:
//
//     Component (vertex) drawing routine
//
// Arguments:
//
//     request - request to be drawn
//     view    - view to draw into
//
{
	MDrawData data = request.drawData();
	apiMeshGeom * geom = (apiMeshGeom*)data.geometry();

	view.beginGL(); 

	// Query current state so it can be restored
	//
	bool lightingWasOn = glIsEnabled( GL_LIGHTING ) ? true : false;
	if ( lightingWasOn ) {
		glDisable( GL_LIGHTING );
	}
	float lastPointSize;
	glGetFloatv( GL_POINT_SIZE, &lastPointSize );

	// Set the point size of the vertices
	//
	glPointSize( POINT_SIZE );

	// If there is a component specified by the draw request
	// then loop over comp (using an MFnComponent class) and draw the
	// active vertices, otherwise draw all vertices.
	//
	MObject comp = request.component();
	if ( ! comp.isNull() ) {
		MFnSingleIndexedComponent fnComponent( comp );
		for ( int i=0; i<fnComponent.elementCount(); i++ )
		{
			int index = fnComponent.element( i );
			glBegin( GL_POINTS );
			MPoint vertex = geom->vertices[ index  ];
			glVertex3f( (float)vertex[0], 
						(float)vertex[1], 
						(float)vertex[2] );
			glEnd();

			char annotation[32];
			sprintf( annotation, "%d", index );
			view.drawText( annotation, vertex );
		}
	}
	else {
		int vid = 0;
		for ( int i=0; i<geom->faceCount; i++ )
		{
			glBegin( GL_POINTS );
			for ( int v=0; v<geom->face_counts[i]; v++ )
			{
				MPoint vertex =
					geom->vertices[ geom->face_connects[vid++] ];
				glVertex3f( (float)vertex[0], 
							(float)vertex[1], 
							(float)vertex[2] );
			}
			glEnd();
		}
	}

	// Restore the state
	//
	if ( lightingWasOn ) {
		glEnable( GL_LIGHTING );
	}
	glPointSize( lastPointSize );

	view.endGL(); 
}

void apiMeshUI::drawBoundingBox( const MDrawRequest & request,
							  M3dView & view ) const
//
// Description:
//
//     Bounding box drawing routine
//
// Arguments:
//
//     request - request to be drawn
//     view    - view to draw into
//
{
	// Get the surface shape
	MPxSurfaceShape *shape = surfaceShape();
	if ( !shape )
		return;
		
	// Get the bounding box	
	MBoundingBox box = shape->boundingBox();
	float w = (float) box.width();
	float h = (float) box.height();
	float d = (float) box.depth();
	view.beginGL(); 
	
	// Below we just two sides and then connect
	// the edges together

	MPoint minVertex = box.min();
	
	// Draw first side
	glBegin( GL_LINE_LOOP );
	MPoint vertex = minVertex;
	glVertex3f( (float)vertex[0],   (float)vertex[1],   (float)vertex[2] );
	glVertex3f( (float)vertex[0]+w, (float)vertex[1],   (float)vertex[2] );
	glVertex3f( (float)vertex[0]+w, (float)vertex[1]+h, (float)vertex[2] );
	glVertex3f( (float)vertex[0],   (float)vertex[1]+h, (float)vertex[2] );	
	glVertex3f( (float)vertex[0],   (float)vertex[1],   (float)vertex[2] );
	glEnd();

	// Draw second side
	MPoint sideFactor(0,0,d);
	MPoint vertex2 = minVertex + sideFactor;
	glBegin( GL_LINE_LOOP );
	glVertex3f( (float)vertex2[0],   (float)vertex2[1],   (float)vertex2[2] );	
	glVertex3f( (float)vertex2[0]+w, (float)vertex2[1],   (float)vertex2[2] );
	glVertex3f( (float)vertex2[0]+w, (float)vertex2[1]+h, (float)vertex2[2] );
	glVertex3f( (float)vertex2[0],   (float)vertex2[1]+h, (float)vertex2[2] );	
	glVertex3f( (float)vertex2[0],   (float)vertex2[1],   (float)vertex2[2] );	
	glEnd();

	// Connect the edges together
	glBegin( GL_LINES );
	glVertex3f( (float)vertex2[0],   (float)vertex2[1],   (float)vertex2[2] );	
	glVertex3f( (float)vertex[0],   (float)vertex[1],   (float)vertex[2] );

	glVertex3f( (float)vertex2[0]+w,   (float)vertex2[1],   (float)vertex2[2] );	
	glVertex3f( (float)vertex[0]+w,   (float)vertex[1],   (float)vertex[2] );

	glVertex3f( (float)vertex2[0]+w,   (float)vertex2[1]+h,   (float)vertex2[2] );	
	glVertex3f( (float)vertex[0]+w,   (float)vertex[1]+h,   (float)vertex[2] );

	glVertex3f( (float)vertex2[0],   (float)vertex2[1]+h,   (float)vertex2[2] );	
	glVertex3f( (float)vertex[0],   (float)vertex[1]+h,   (float)vertex[2] );
	glEnd();
	
	view.endGL(); 
}

bool apiMeshUI::selectVertices( MSelectInfo &selectInfo,
							 MSelectionList &selectionList,
							 MPointArray &worldSpaceSelectPts ) const
//
// Description:
//
//     Vertex selection.
//
// Arguments:
//
//     selectInfo           - the selection state information
//     selectionList        - the list of selected items to add to
//     worldSpaceSelectPts  -
//
{
	bool selected = false;
	M3dView view = selectInfo.view();

	MPoint 		xformedPoint;
	MPoint 		selectionPoint;
	double		z,previousZ = 0.0;
 	int			closestPointVertexIndex = -1;

	const MDagPath & path = selectInfo.multiPath();

	// Create a component that will store the selected vertices
	//
	MFnSingleIndexedComponent fnComponent;
	MObject surfaceComponent = fnComponent.create( MFn::kMeshVertComponent );
	int vertexIndex;

	// if the user did a single mouse click and we find > 1 selection
	// we will use the alignmentMatrix to find out which is the closest
	//
	MMatrix	alignmentMatrix;
	MPoint singlePoint; 
	bool singleSelection = selectInfo.singleSelection();
	if( singleSelection ) {
		alignmentMatrix = selectInfo.getAlignmentMatrix();
	}

	// Get the geometry information
	//
	apiMesh* meshNode = (apiMesh*)surfaceShape();
	apiMeshGeom * geom = meshNode->meshGeom();

	// Loop through all vertices of the mesh and
	// see if they lie withing the selection area
	//
	int numVertices = geom->vertices.length();
	for ( vertexIndex=0; vertexIndex<numVertices; vertexIndex++ )
	{
		MPoint currentPoint = geom->vertices[ vertexIndex ];

		// Sets OpenGL's render mode to select and stores
		// selected items in a pick buffer
		//
		view.beginSelect();

		glBegin( GL_POINTS );
		glVertex3f( (float)currentPoint[0], 
					(float)currentPoint[1], 
					(float)currentPoint[2] );
		glEnd();

		if ( view.endSelect() > 0 )	// Hit count > 0
		{
			selected = true;

			if ( singleSelection ) {
				xformedPoint = currentPoint;
				xformedPoint.homogenize();
				xformedPoint*= alignmentMatrix;
				z = xformedPoint.z;
				if ( closestPointVertexIndex < 0 || z > previousZ ) {
					closestPointVertexIndex = vertexIndex;
					singlePoint = currentPoint;
					previousZ = z;
				}
			} else {
				// multiple selection, store all elements
				//
				fnComponent.addElement( vertexIndex );
			}
		}
	}

	// If single selection, insert the closest point into the array
	//
	if ( selected && selectInfo.singleSelection() ) {
		fnComponent.addElement(closestPointVertexIndex);

		// need to get world space position for this vertex
		//
		selectionPoint = singlePoint;
		selectionPoint *= path.inclusiveMatrix();
	}

	// Add the selected component to the selection list
	//
	if ( selected ) {
		MSelectionList selectionItem;
		selectionItem.add( path, surfaceComponent );

		MSelectionMask mask( MSelectionMask::kSelectComponentsMask );
		selectInfo.addSelection(
			selectionItem, selectionPoint,
			selectionList, worldSpaceSelectPts,
			mask, true );
	}

	return selected;
}
